# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: leon

import datetime
from collections import namedtuple
import pathlib
import os

import jinja2
import pandas

from coin.base.datetime_util import to_datetime
from coin.exchange.okex_futures.kr_rest.futures_product import OkexFuturesProduct
from coin.exchange.okex_futures.kr_rest.currency import OkexCurrency
from coin.support.feed_tool.feed_stats.logic.util import datetime_span
from coin.support.feed_tool.feed_stats.logic.util import (gen_engine_for_instrument_feed_querier,
                                                          get_exchange_api_id_from_request)
from coin.support.feed_tool.feed_stats.logic.instrument_stats_calculator import (Keys,
                                                                                 Values,
                                                                                 InstrumentStats)

instrument_extracted_columns = ['fetch_time', 'symbol', 'column', 'value']

DbInstrumentExtracted = namedtuple('DbInstrumentExtracted', instrument_extracted_columns)


def get_product_from_columns(product_type, symbol, fetch_time):
  if product_type == 'Futures':
    fetch_time = datetime.datetime.strptime(fetch_time, '%Y-%m-%d %H:%M:%S.%f')
    return OkexFuturesProduct.FromStrProduct(symbol, fetch_time)
  elif product_type == 'Currency':
    return OkexCurrency.FromStrCurrency(symbol)
  else:
    raise ValueError(product_type, symbol, fetch_time)


class DbInstrumentStatsCalculator():
  def __init__(self, trading_date, machine, subscription_request):
    self._trading_date = trading_date.strftime('%Y%m%d')
    self._machine = machine
    self._exchange = subscription_request.exchange
    self._exchange_api_id = get_exchange_api_id_from_request([subscription_request])[0]
    self._instruments = {}
    self._column_names = ['long_short_position_account_ratio', 'long_short_margin_ratio']
    self._instrument_stats = []

  @property
  def instrument_stats(self):
    return self._instrument_stats

  def set_product(self, symbol):
    self._instruments[symbol] = []

  def _gen_and_append_stats_row(self, df, start, end, column_name, symbol, time_dimension):
    time_fmt = '%Y-%m-%d %H:%M:%S.%f'
    df = df.between_time(start.time(), end.time())
    df = df.loc[df['column'] == column_name]
    describe = df['value'].describe().tolist()
    if len(describe) < len(Values):
      describe = [None] * len(Values)
    start = start.strftime(time_fmt)
    end = end.strftime(time_fmt)
    self._instrument_stats.append(
        InstrumentStats(self._trading_date,
                        self._machine,
                        self._exchange_api_id,
                        symbol,
                        column_name,
                        time_dimension,
                        start,
                        end,
                        *describe))

  def hour_stats(self, df, symbol):
    start = df.iloc[0]['fetch_time'].to_pydatetime()
    end = df.iloc[-1]['fetch_time'].to_pydatetime()
    for column_name in self._column_names:
      prev_dt = start
      start_hour = start.hour
      start_hour = start.replace(hour=start_hour + 1, minute=0, second=0, microsecond=0)
      end_hour = end.replace(minute=0, second=0, microsecond=0)
      for dt in datetime_span(start_hour, end_hour, datetime.timedelta(hours=1)):
        self._gen_and_append_stats_row(df, prev_dt, dt, column_name, symbol, 'hour')
        prev_dt = dt

      # For the last hour.
      self._gen_and_append_stats_row(df, prev_dt, end, column_name, symbol, 'hour')

  def daily_stats(self, df, symbol):
    start = df.iloc[0]['fetch_time'].to_pydatetime()
    end = df.iloc[-1]['fetch_time'].to_pydatetime()
    for column_name in self._column_names:
      self._gen_and_append_stats_row(df, start, end, column_name, symbol, 'daily')

  def calculator_stats(self):
    self._instrument_stats = []
    for symbol in self._instruments.keys():
      df = pandas.DataFrame(self._instruments[symbol], columns=instrument_extracted_columns)
      df['datetime'] = pandas.to_datetime(df['fetch_time'], format='%Y-%m-%d %H:%M:%S.%f')
      df = df.set_index('datetime')
      if len(df) == 0:
        continue
      self.hour_stats(df, symbol)
      self.daily_stats(df, symbol)

  def extract_feed_from_db(self):
    long_short_position_account_ratio = self.query_long_short_position_account_ratio()
    for _, row in long_short_position_account_ratio.iterrows():
      product_type = row['product_type']
      symbol = row['symbol']
      fetch_time = row['fetch_time']
      data = eval(row['json'])['data']
      buy = data['buydata'][-1]
      sell = data['selldata'][-1]
      ratio = buy / sell
      self._instruments.setdefault(symbol, [])
      self._instruments[symbol].append(
          DbInstrumentExtracted(fetch_time, symbol, 'long_short_position_account_ratio', ratio))

    long_short_margin_ratio = self.query_long_short_margin_ratio()
    for _, row in long_short_margin_ratio.iterrows():
      product_type = row['product_type']
      symbol = row['symbol']
      fetch_time = row['fetch_time']
      data = eval(row['json'])['data']
      buy = data['buydata'][-1]
      sell = data['selldata'][-1]
      ratio = buy / sell
      self._instruments.setdefault(symbol, [])
      self._instruments[symbol].append(
          DbInstrumentExtracted(fetch_time, symbol, 'long_short_margin_ratio', ratio))

  def _gen_query_column_sql(self, column):
    template_loader = jinja2.FileSystemLoader(searchpath="./")
    template_env = jinja2.Environment(loader=template_loader)

    sql_tmpl = 'coin/support/feed_tool/feed_stats/query_sql/' \
               'query_okex_instrument_feed.sql.tmpl'
    template = template_env.get_template(sql_tmpl)
    return template.render(trading_date=self._trading_date, machine=self._machine, column=column)

  def query_long_short_position_account_ratio(self):
    engine = gen_engine_for_instrument_feed_querier()
    feed = pandas.read_sql(self._gen_query_column_sql('long_short_position_account_ratio'), engine)
    return feed

  def query_long_short_margin_ratio(self):
    engine = gen_engine_for_instrument_feed_querier()
    feed = pandas.read_sql(self._gen_query_column_sql('long_short_margin_ratio'), engine)
    return feed

  def _sample_and_merge(self, df_long_short_position_account_ratio, df_long_short_margin_ratio):
    df_long_short_position_account_ratio = \
        df_long_short_position_account_ratio.resample('T', closed='left', label='left').last()
    df_long_short_margin_ratio = \
      df_long_short_margin_ratio.resample('T', closed='left', label='left').last()

    df = pandas.merge(df_long_short_position_account_ratio,
                      df_long_short_margin_ratio,
                      how='inner',
                      left_index=True,
                      right_index=True)
    return df

  def _prepare_df(self, symbol, column):
    df = pandas.DataFrame(self._instruments[symbol], columns=instrument_extracted_columns)
    df = df.loc[df['column'] == column]
    df = df[['fetch_time', 'value']]
    df.rename(columns={'value': column}, inplace=True)
    df['datetime'] = \
        pandas.to_datetime(df['fetch_time'], format='%Y-%m-%d %H:%M:%S.%f')
    df.drop('fetch_time', axis=1, inplace=True)
    df = df.set_index('datetime')
    return df

  def generate_csv(self, output_csv_root):
    for symbol in self._instruments.keys():
      df_long_short_position_account_ratio = \
          self._prepare_df(symbol, 'long_short_position_account_ratio')
      df_long_short_margin_ratio = \
          self._prepare_df(symbol, 'long_short_margin_ratio')

      df = self._sample_and_merge(df_long_short_position_account_ratio, df_long_short_margin_ratio)

      dir = os.path.join(output_csv_root, self._machine, self._trading_date)
      pathlib.Path(dir).mkdir(parents=True, exist_ok=True)

      df.to_csv(os.path.join(dir, '%s_%s.csv' % (self._exchange, symbol)),
                float_format='%12.6f',
                index=True)
